﻿using System;
using System.Collections.Generic;
using System.Timers;

namespace cz.zcu.PreSti
{
    abstract public class RunnableComponent : BaseComponent, IComparable
    {
        private List<Trigger>[] trigOuts;
        private int trigInCount;
        protected IPresentation presentation;

        protected Timer runTimer;
        protected int delay;
        protected int priority = 0;

        public RunnableComponent(IPresentation p) : this(p, 1, 1, 0, 0) { }
        public RunnableComponent(IPresentation p, int trigInCount, int trigOutCount, int settersCount, int gettersCount) : base(settersCount, gettersCount)
        {
            presentation = p;
            this.trigInCount = trigInCount;
            trigOuts = new List<Trigger>[trigOutCount];
            for (int i = 0; i < trigOutCount; i++)
                trigOuts[i] = new List<Trigger>();

            runTimer = new Timer();
            runTimer.Elapsed += new ElapsedEventHandler(timeElapsed);
            runTimer.AutoReset = false;
        }

        #region properties

        public List<Trigger> TrigOuts
        {
            get
            {
                List<Trigger> l = new List<Trigger>();
                foreach (List<Trigger> tl in trigOuts)
                {
                    foreach (Trigger t in tl)
                    {
                        l.Add(t);
                    }
                }
                return l;
            }
        }

        public int Delay
        {
            get
            {
                return delay;
            }
            set
            {
                delay = value;
            }
        }

        public int Priority
        {
            get
            {
                return priority;
            }
            set
            {
                priority = value;
            }
        }

        #endregion 

        public abstract void update();

        public virtual void draw() { }

        public virtual void trigIn(int n)
        {
            runTimer.Stop();
            this.retrieveValuesFromGetters();
            if (delay == 0)
            {
                if (Finished != null) Finished(this, EventArgs.Empty);
                return;
            }
            update();
            runTimer.Interval = delay;
            runTimer.Start();
            if (!presentation.layers.Contains(this)) presentation.layers.Add(this);
            if(Redraw != null) Redraw(this, EventArgs.Empty);
        }

        protected void trigOut(int n)
        {
            foreach (Trigger trig in trigOuts[n])
            {
                System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(trig.start));
                t.Start();
            }
        }

        public void setTrigOut(RunnableComponent component)
        {
            setTrigOut(component, 0);
        }

        public void setTrigOut(RunnableComponent component, int delay)
        {
            setTrigOut(component, 0, delay);
        }

        public void setTrigOut(RunnableComponent component, int trigN, int delay)
        {
            setTrigOut(0, component, trigN, delay);
        }

        public void setTrigOut(int n, RunnableComponent component, int trigN, int delay)
        {
            trigOuts[n].Add(new Trigger(component, trigN, delay));
        }

        public void terminate()
        {
            runTimer.Stop();
            presentation.layers.Remove(this);
            if(Finished != null) Finished(this, EventArgs.Empty);
            if (Redraw != null) Redraw(this, EventArgs.Empty);
        }

        protected void timeElapsed(object sender, ElapsedEventArgs e)
        {
            terminate();
        }

        public event EventHandler Finished;

        public event EventHandler Redraw;

        public override void setValueToSetter(int setterN, object value)
        {
            switch (setterN)
            {
                case 0:
                    convertValue(ref delay, value); break;
            }
        }

        #region IComparable Members

        public int CompareTo(object obj)
        {
            if (this.Priority == ((RunnableComponent)obj).Priority)
                if (this.GetHashCode() > obj.GetHashCode()) return 1; else return -1;
            if (this.Priority > ((RunnableComponent)obj).Priority) return 1; else return -1;
        }

        #endregion

        public class Trigger {
            private RunnableComponent component;
            private int trigN;
            private Timer timer = null;

            public Trigger(RunnableComponent component) : this(component, 0) { }
            public Trigger(RunnableComponent component, int trinN) : this(component, trinN, 0) { }
            public Trigger(RunnableComponent component, int trigN, int delay)
            {
                this.component = component;
                this.trigN = trigN;
                if (delay > 0)
                {
                    timer = new Timer();
                    timer.AutoReset = false;
                    timer.Interval = delay;
                    timer.Elapsed += new ElapsedEventHandler(timeElapsed);
                }
            }

            public RunnableComponent Component
            {
                get
                {
                    return component;
                }

            }

            public int Delay
            {
                get
                {
                    return (int)timer.Interval;
                }
                set
                {
                    timer.Interval = value;
                }
            }

            private void timeElapsed(object sender, ElapsedEventArgs e)
            {
                trig();
            }

            private void trig()
            {
                component.trigIn(trigN);
            }

            public void start()
            {
                if (timer == null)
                    trig();
                else
                {
                    timer.Start();
                    // Preload values in waiting time
                    component.retrieveValuesFromGetters();
                    component.update();
                }
            }

            public void terminate()
            {
                if (timer != null) timer.Stop();
            }
        }
    }
}
